<?php

/**
 * Generic application bootstrap.
 *
 * This should be the very first plugin to be registered. Ideally, this would
 * be the only plugin registered in the application bootstrap, and the rest of
 * the plugins needed by the application is registered by the functions/methods
 * called by this plugin.
 *
 * @author    Mon Zafra <monzee at gmail>
 * @copyright (c)2009 Mon Zafra
 * @category  Mz
 * @license   http://mz-project.googlecode.com/svn/trunk/LICENSE     MIT License
 * @version   SVN: $Id: Bootstrapper.php 13 2009-02-28 13:44:33Z monzee $
 */
class Mz_Bootstrapper extends Zend_Controller_Plugin_Abstract
{
    const SET = 'set';
    const PREPEND = 'prepend';
    const APPEND = 'append';

    protected $_app;
    protected $_methods;
    protected $_defaultMethods = array(
        'setupConfig', 'setupDatabase', 'setupModules', 'setupPlugins',
        'setupController', 'setupView', 'setupLayout'
    );

    /**
     * Constructor.
     *
     * @param array  $methods Methods/functions to execute. Each element should
     *                        be a valid callback or a method name of the $app
     *                        object. Order is important!
     * @param mixed  $app     Instance (or name for static methods) of the class
     *                        where the methods to  call are. If null, global
     *                        namespace functions are used.
     * @param string $mode    Whether to replace, append, or prepend to the list of
     *                        default methods
     * @todo An option to use reflection to discover bootstrap functions
     */
    public function __construct(array $methods = null, $app = null, $mode = self::SET)
    {
        $this->setApp($app);
        $this->setMethods($methods, $mode);
    }

    /**
     * Sets the class where the bootstrap methods are.
     *
     * See constructor notes.
     *
     * @param mixed $app 
     */
    public function setApp($app)
    {
        $this->_app = $app;
    }

    /**
     * Sets the bootstrap methods to be called.
     *
     * Call with no params to reset to default.
     * See constructor notes
     *
     * @param array  $methods
     * @param string $mode
     */
    public function setMethods(array $methods = null, $mode = self::SET)
    {
        if (null === $methods) {
            $this->_methods = $this->_defaultMethods;
        } else {
            switch ($mode) {
                case self::PREPEND:
                    $this->_methods = array_merge($methods, $this->_defaultMethods);
                    break;
                case self::APPEND;
                    $this->_methods = array_merge($this->_defaultMethods, $methods);
                    break;
                default:
                    $this->_methods = $methods;
                    break;
            }
        }
    }

    /**
     * Initializes the application by calling the registered bootstrap methods.
     *
     * @param Zend_Controller_Request_Abstract $request
     */
    public function routeStartup(Zend_Controller_Request_Abstract $request)
    {
        $methods = $this->_methods;
        foreach ($methods as $method) {
            if (is_callable($method)) {
                call_user_func($method);
            } else {
                $this->$method(); // use magic
            }
        }
    }

    /**
     * Proxies missing calls to application class or global namespace.
     *
     * @param string $method
     * @param array  $args
     * @return mixed
     * @todo throw exception if the method is not callable?
     */
    public function __call($method, $args)
    {
        // if _app property is not set, proxy call to global namespace function
        $callback = null === $this->_app ? $method : array($this->_app, $method);
        return call_user_func_array($callback, $args);
    }

}
